- Senior Software Engineer
- CDI co-spec lead, Java EE 8 EG
- Red Hat, Inc.
- @antoine_sd
- www.next-presso.com
- github.com/antoinesd
| Slides available at rafabene.github.io/deltaspike-cdi-toolbox/ |
| OCP (Open Closed Principle) in CDI |
| To integrate 3rd party libraries, frameworks or legacy components |
| To change existing configuration or behavior |
| To extend CDI and Java EE |
| Thanks to them, Java EE can evolve between major releases |
| Implement javax.enterprise.inject.spi.Extension |
| Register the Extension |
| Observe SPI events at boot time related to the bean manager lifecycle |
Service provider of the service javax.enterprise.inject.spi.Extension declared in META-INF/services |
| Just put the fully qualified name of your extension class in this file |
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.Extension;
public class CdiExtension implements Extension {
void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd) {
}
//...
void afterDeploymentValidation(@Observes AfterDeploymentValidation adv) {
}
} @Inject @Property("key1")
private String property1;
@Inject @Property("key2")
private String property2;| It can be achieved by @Produces but it could lead to: Unsatisfied dependencies for type String with qualifiers @Property… |
@Produces
@Property("key1")
public String propriedade1Producer()
{
return propertiesFile.getProperty("key1");
}| One of the most powerful feature of the CDI specification |
| Not really popularized, partly due to: |
| CDI is a specification. It doesn’t provide business features |
| but it includes a powerful hook to add these business features |
| The "Poortable extensions" feature is this hook |
| Thanks to it, CDI can be easily enhanced with new high level features |
| A collection of ready to use extensions to help you in your projects |
| A toolbox to help you develop new CDI portable extensions |
| A great way to learn how to develop your own extension by browsing the source code |
| The most obvious entry point to CDI eco-system |
TODO Move content to here
TODO Move content to here
TODO Move content to here
public class InventoryActions {
@PersistenceContext private EntityManager em;
@Inject private Event<ExceptionToCatchEvent> catchEvent; (1)
public Integer queryForItem(Item item) {
try {
Query q = em.createQuery("SELECT i from Item i where i.id = :id");
q.setParameter("id", item.getId());
return q.getSingleResult();
} catch (PersistenceException e) {
catchEvent.fire(new ExceptionToCatchEvent(e)); (2)
}
}
}| 1 | The Event of generic type ExceptionToCatchEvent is injected into your class for use later within a try/catch block. |
| 2 | The event is fired with a new instance of ExceptionToCatchEvent constructed with the exception to be handled. |
| Exceptions are handled asynchronously. |
@ExceptionHandler (1)
public class MyHandlers {
void printExceptions(@Handles ExceptionEvent<Throwable> evt) { (2)
System.out.println("Something bad happened:" +
evt.getException().getMessage());
evt.handleAndContinue(); (3)
}
}| 1 | Exception handler methods are registered on beans annotated with @ExceptionHandler |
| 2 | The @Handles annotation on the first parameter designates this method as an exception handler. |
| 3 | This handler does not modify the invocation of subsequent handlers, as designated by invoking handleAndContinue(). |
| The current ProjectStage can be injected. |
@Inject
private ProjectStage projectStage;
//...
boolean isDevProjectStage = ProjectStage.Development.equals(this.projectStage);| You can also use the ProjectStage at XHTML files. |
<h:panelGroup layout="block"rendered="#{applicationConfig.projectStage == 'Development'}" >
<!-- HTML Snippet is shown only in Development stage -->
</h:panelGroup>| Besides custom ProjectStages it is possible to use the following pre-defined ProjectStages: |
| It can be set using DeltaSpike Configuration |
-D org.apache.deltaspike.ProjectStage=Development| It’s like @Vetoed from CDI 1.1 but better! |
@Exclude
public class NoBean{ }@Exclude(ifProjectStage = ProjectStage.Development.class)
public class MyBean{ }@Exclude(exceptIfProjectStage = ProjectStage.Development.class)
public class MyDevBean{ }@Exclude(onExpression = "db==prodDB")
public class DevDbBean { }String userName = ConfigResolver.getPropertyValue("user.name"); (1)
String dbUserName = ConfigResolver.getPropertyAwarePropertyValue("db.username"); (2)
Integer dbPort = ConfigResolver
.resolve("db.port") (3)
.as(Integer.class)
.withProjectStage(true)
.withDefault(3306)
.getValue();
Date deadline = ConfigResolver.resolve("project.deadline") (4)
.as(Date.class, new CustomDateConverter()).getValue());user.name = "Rafael" (1)
db.username.Production = "Antoine" (2)
db.username.Development = "Benevides" (2)
db.port = 1234 (3)
project.deadline = 2017-04-01 (4)@ApplicationScoped
public class SomeRandomService
{
@Inject
@ConfigProperty(name = "endpoint.poll.interval")
private Integer pollInterval;
@Inject
@ConfigProperty(name = "endpoint.poll.servername")
private String pollUrl;
...
}How to provide these Properties to DeltaSpike?
| By default there are implementations for the following configuration sources (listed in the lookup order): |
| You can also specify your own config file or create a custom ConfigSource (example: read from XML, JSON, DB, etc) |
| DeltaSpike has simple APIs for performing basic resource loading and property file reading. |
@Inject
@InjectableResource("myfile.properties")
private InputStream is;
public String getVersion() throws IOException {
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
return br.readLine();
}
}| The InjectableResourceProvider interface can be implemented to allow reading from alternate sources if needed (e.g. database LOBs, NoSQL storage areas). |
| Type-safe messages - Bean creation |
@Named("msg")
@MessageBundle
public interface MyMessages {
public String welcome();
//in the message bundle: welcometo=Welcome to %s
public String welcomeTo(String username);
//in the message bundle: custom_message=DeltaSpike is awesome!
@MessageTemplate("{custom_message}")
public String message();
}
--> Create the Bundle files in the same package <--
org/apache/deltaspike/example/message/MyMessages.properties
org/apache/deltaspike/example/message/MyMessages_en.properties
org/apache/deltaspike/example/message/MyMessages_de.properties| Now the messages bean is ready to be used in Java Classes |
@Inject
private MyMessages messages;
//
new FacesMessage(messages.welcomeTo("Rafael"));
log.info(messages.message());| …or even inside JSF because it uses a @Named annotation. |
<h1>#{msg.welcome}</h1>| Create the Annotation and the authorizer |
@Retention(value = RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@SecurityBindingType
public @interface AdminOnly {
}
@ApplicationScoped
public class ApplicationAuthorizer
{
@Secures
@AdminOnly
public boolean verifyPermission(InvocationContext invocationContext, BeanManager manager, @Loggged User user) throws Exception {
return user.getRole().equalsIgnoreCase("Admin");
}
}| Now this annotation can be used in any method |
@ApplicationScoped
public class SecuredBean {
@AdminOnly
public void doSomething() {
//...
}
}| Data module is an implementation of the repository pattern. |
| At the moment it only support RDBMS thru JPA. |
| But it could be extended to support other data services. |
"A Repository represents
all objects of a certain type
as a conceptual set.
It acts like a collection,
except with more elaborate
querying capability."
| -Eric Evans (in Domain Driven Design) |
@Repository
public interface UserRepository extends EntityRepository<User, Long> {
/* DeltaSpike creates a proxy which implements:
count();
findAll();
findBy(PK);
flush();
refresh();
remove();
save();
saveAndFlush();
*/
}| It uses the “partial bean” module to dynamically create implementation at runtime. |
@Repository
public interface UserRepository extends EntityRepository<User, Long> {
public User findByUsernameAndPassword(String username, char[] password); (1)
@Query("SELECT u FROM User AS u WHERE u.role in (?1)") (2)
public List<Role> findByRoles(List<Role> roles);
}| 1 | The name of the method automatically creates the query. Example: "SELECT u FROM User u WHERE u.username = ?1 AND u.password = ?2 " |
| 2 | The query is defined inside the @Query annotation. |
@MessageBundle
public interface Messages {
@MessageTemplate("Welcome to DeltaSpike")
String welcomeToDeltaSpike();
}
@Model
public class MyJSFBean {
@Inject
private JsfMessage<Messages> messages;
//...
messages.addInfo().welcomeToDeltaSpike();
}| "The window-scope is like a session per window" |
@WindowScoped
public class PreferencesBean implements Serializable {
//...
}| "There isn’t a lot of use-cases which need shared data between windows" |
| "To avoid that the same content of a form gets submitted and therefore processed multiple times" |
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ds="http://deltaspike.apache.org/jsf">
<h:head>
<!-- head content -->
</h:head>
<h:body>
<h:form>
<!-- form content -->
<ds:preventDoubleSubmit/>
</h:form>
</h:body>
</html>| Provides integration with Quartz. |
// Job will execute each minute
@Scheduled(cronExpression = "0 0/1 * * * ?", onStartup = false)
public class CdiAwareQuartzJob implements org.quartz.Job {
// And it can receive CDI injections
@Inject
private AdminServices service;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
service.executeAdministrativeTask();
}
}@Inject
private Scheduler<Job> jobScheduler;
//...
jobScheduler.registerNewJob(CdiAwareQuartzJob.class);